﻿Option Strict On
Option Explicit On

Imports System.Drawing

Public Structure Vector2
    Implements IEquatable(Of Vector2)
    Implements IComparable(Of Vector2)
    Implements IComparable

    Public Shared ReadOnly Empty As New Vector2

    Public Shared Function AreVectorsCollinear(ByVal vectorA As Vector2, ByVal vectorB As Vector2, ByVal vectorC As Vector2, ByVal tolerance As Single) As Boolean
        Return Geometry.Between(GetArea(vectorA, vectorB, vectorC), -tolerance, tolerance)
    End Function

    Public Shared Function GetAngleTo(ByVal source As Vector2, ByVal target As Vector2) As Single
        Return Geometry.RadiansToDegrees(Vector2.GetRadiansTo(source, target))
    End Function

    Public Shared Function GetArea(ByVal vectorA As Vector2, ByVal vectorB As Vector2, ByVal vectorC As Vector2) As Single
        Return vectorA.X * (vectorB.Y - vectorC.Y) + vectorB.X * (vectorC.Y - vectorA.Y) + vectorC.X * (vectorA.Y - vectorB.Y)
    End Function

    Public Shared Function GetCrossProduct(ByVal source As Vector2, ByVal target As Vector2) As Single
        Return source.X * target.Y - source.Y * target.X
    End Function

    Public Shared Function GetDistance(ByVal source As Vector2, ByVal target As Vector2) As Single
        Dim squareX As Double = CDbl(target.X - source.X)
        squareX *= squareX
        Dim squareY As Double = CDbl(target.Y - source.Y)
        squareY *= squareY
        Return CSng(System.Math.Sqrt(squareX + squareY))
    End Function

    Public Shared Function GetDotProduct(ByVal source As Vector2, ByVal target As Vector2) As Single
        Return source.X * target.X + source.Y * target.Y
    End Function

    Public Shared Function GetMagnitude(ByVal source As Vector2) As Single
        Return Geometry.Sqrt(Geometry.Square(source.X) + Geometry.Square(source.Y))
    End Function

    Public Shared Function GetNormalized(ByVal source As Vector2) As Vector2
        Dim delta As Single = Vector2.GetMagnitude(source)
        Return New Vector2(source.X / delta, source.Y / delta)
    End Function

    Public Shared Function GetRadiansTo(ByVal source As Vector2, ByVal target As Vector2) As Single
        Return Geometry.WrapRadians(Geometry.Atan2(target.Y - source.Y, target.X - source.X))
    End Function

    Public Shared Function GetSlope(vectorOne As Vector2, vectorTwo As Vector2) As Single
        Return (vectorTwo.Y - vectorOne.Y) / (vectorTwo.X - vectorOne.X)
    End Function

    Public Shared Function GetVectorToward(ByVal source As Vector2, ByVal target As Vector2, ByVal distance As Single) As Vector2
        Dim angle As Single = Vector2.GetRadiansTo(source, target)
        Return GetVectorToward(source, angle, distance, AngleUnit.Radians)
    End Function

    Public Shared Function GetVectorToward(ByVal source As Vector2, ByVal angle As Single, ByVal distance As Single, unit As AngleUnit) As Vector2
        If unit = AngleUnit.Degrees Then angle = Geometry.DegreesToRadians(angle)
        Return source + New Vector2(Geometry.Cos(angle) * distance, Geometry.Sin(angle) * distance)
    End Function

    Public X As Single
    Public Y As Single

    Public Property Horizontal As Single
        Get
            Return X
        End Get
        Set(ByVal value As Single)
            X = value
        End Set
    End Property

    Public Property Vertical As Single
        Get
            Return Y
        End Get
        Set(ByVal value As Single)
            Y = value
        End Set
    End Property

    Public Sub New(ByVal xValue As Single, ByVal yValue As Single)
        X = xValue
        Y = yValue
    End Sub

    Public Sub New(ByVal bothValues As Single)
        Me.New(bothValues, bothValues)
    End Sub

    Public Sub New(ByVal pointValue As PointF)
        Me.New(pointValue.X, pointValue.Y)
    End Sub

    Public Sub New(ByVal pointValue As Point)
        Me.New(CSng(pointValue.X), CSng(pointValue.Y))
    End Sub

    Public Function IsInfinite() As Boolean
        Return Single.IsInfinity(X) OrElse Single.IsInfinity(Y)
    End Function

    Public Function DistanceTo(ByVal target As Vector2) As Single
        Return Vector2.GetDistance(Me, target)
    End Function

    Public Function AngleTo(ByVal target As Vector2) As Single
        Return Vector2.GetAngleTo(Me, target)
    End Function

    Public Function RadiansTo(ByVal target As Vector2) As Single
        Return Vector2.GetRadiansTo(Me, target)
    End Function

    Public Function VectorToward(ByVal target As Vector2, ByVal distance As Single) As Vector2
        Return Vector2.GetVectorToward(Me, target, distance)
    End Function

    Public Function VectorToward(ByVal angle As Single, ByVal distance As Single, unit As AngleUnit) As Vector2
        Return Vector2.GetVectorToward(Me, angle, distance, unit)
    End Function

    Public Function Magnitude() As Single
        Return Vector2.GetMagnitude(Me)
    End Function

    Public Function Normalize() As Vector2
        Return Vector2.GetNormalized(Me)
    End Function

    Public Function ToPoint() As Point
        Return New Point(CInt(X), CInt(Y))
    End Function

    Public Function ToPointF() As PointF
        Return New PointF(X, Y)
    End Function

    Public Overrides Function ToString() As String
        Return String.Format("[X={0:n2}/Y={1:n2}]", X, Y)
    End Function

    Public Function Truncate() As Point
        Return New Point(CInt(System.Math.Floor(X)), CInt(System.Math.Floor(Y)))
    End Function

    Public Overloads Function ToString(ByVal format As String) As String
        Return String.Format(format, X, Y)
    End Function

    Public Shared Operator =(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return source.X = target.X AndAlso source.Y = target.Y
    End Operator

    Public Shared Operator <>(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return Not (source = target)
    End Operator

    Public Shared Operator +(ByVal source As Vector2, ByVal target As Vector2) As Vector2
        Return New Vector2(source.X + target.X, source.Y + target.Y)
    End Operator

    Public Shared Operator -(ByVal source As Vector2, ByVal target As Vector2) As Vector2
        Return New Vector2(source.X - target.X, source.Y - target.Y)
    End Operator

    Public Shared Operator *(ByVal source As Vector2, ByVal target As Vector2) As Vector2
        Return New Vector2(source.X * target.X, source.Y * target.Y)
    End Operator

    Public Shared Operator /(ByVal source As Vector2, ByVal target As Vector2) As Vector2
        Return New Vector2(source.X / target.X, source.Y / target.Y)
    End Operator

    Public Shared Operator +(ByVal source As Vector2, ByVal target As Single) As Vector2
        Return New Vector2(source.X + target, source.Y + target)
    End Operator

    Public Shared Operator -(ByVal source As Vector2, ByVal target As Single) As Vector2
        Return New Vector2(source.X - target, source.Y - target)
    End Operator

    Public Shared Operator *(ByVal source As Vector2, ByVal target As Single) As Vector2
        Return New Vector2(source.X * target, source.Y * target)
    End Operator

    Public Shared Operator /(ByVal source As Vector2, ByVal target As Single) As Vector2
        Return New Vector2(source.X / target, source.Y / target)
    End Operator

    Public Shared Operator >(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return source.CompareTo(target) > 0
    End Operator

    Public Shared Operator <(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return source.CompareTo(target) < 0
    End Operator

    Public Shared Operator >=(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return source.CompareTo(target) >= 0
    End Operator

    Public Shared Operator <=(ByVal source As Vector2, ByVal target As Vector2) As Boolean
        Return source.CompareTo(target) <= 0
    End Operator

    Public Shared Widening Operator CType(ByVal source As Vector2) As PointF
        Return source.ToPointF
    End Operator

    Public Shared Widening Operator CType(ByVal source As Vector2) As Point
        Return source.ToPoint
    End Operator

    Public Shared Widening Operator CType(ByVal source As PointF) As Vector2
        Return New Vector2(source.X, source.Y)
    End Operator

    Public Shared Widening Operator CType(ByVal source As Point) As Vector2
        Return New Vector2(CSng(source.X), CSng(source.Y))
    End Operator

    Public Overloads Function Equals(ByVal other As Vector2) As Boolean Implements System.IEquatable(Of Vector2).Equals
        Return Me = other
    End Function

    Public Function CompareTo(ByVal other As Vector2) As Integer Implements System.IComparable(Of Vector2).CompareTo
        Dim thisMag As Single = Magnitude()
        Dim otherMag As Single = other.Magnitude()
        If thisMag = otherMag Then
            Return (X + Y).CompareTo(other.X + other.Y)
        End If
        Return thisMag.CompareTo(otherMag)
    End Function

    Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
        If TypeOf obj Is Vector2 Then
            Return CompareTo(DirectCast(obj, Vector2))
        ElseIf TypeOf obj Is PointF Then
            Return CompareTo(New Vector2(DirectCast(obj, PointF)))
        ElseIf TypeOf obj Is Point Then
            Return CompareTo(New Vector2(DirectCast(obj, Point)))
        ElseIf TypeOf obj Is IComparable Then
            Return DirectCast(obj, IComparable).CompareTo(Me)
        Else
            Return -1
        End If
    End Function
End Structure

